﻿<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<title>Identity Server Integration - Identity Server 集成</title>
<link type="text/css" rel="stylesheet" href="../bootstrap.min.css" />
</head>

<body>

<div class="document-contents">

<h3>Introduction - 介绍</h3>
<p><a href="http://identityserver.io/" target="_blank">Identity Server</a> is an 
open source <strong>OpenID Connect</strong> and <strong>OAuth 2.0</strong> framework. It can be used to make your 
application an <strong>authentication / single sign on server</strong>. It can also issue 
<strong>access tokens</strong> for 3rd party clients. This document describes 
how you can integrate IdentityServer4 (version <strong>2.0+</strong>) to your project. </p>

<p class="translation">
    <a href="http://identityserver.io/" target="_blank">Identity Server</a> 是一个开源的 <strong>OpenID Connect</strong> and <strong>OAuth 2.0</strong> 框架。
    它可以被用来为你的应用程序提供<strong>认证/单击登录服务器</strong>。它还可以为第三方客户端发行<strong>访问令牌</strong>。
    本文档描述了如何在你的项目中集成 IdentityServer4（version <strong>2.0+</strong>）。
</p>

	<p>Since EF Core package already depends on the first one, you can only install
	<a href="https://www.nuget.org/packages/Abp.ZeroCore.IdentityServer4.EntityFrameworkCore" target="_blank">
	<strong>Abp.ZeroCore.IdentityServer4.EntityFrameworkCore</strong></a> 
	package to your project. Install it to the project contains your DbContext 
    (.EntityFrameworkCore project for default templates):</p>
    <p class="translation">
        由于 EF Core 包依赖于第一个包，所以你可以在你的项目中只安装 <a href="https://www.nuget.org/packages/Abp.ZeroCore.IdentityServer4.EntityFrameworkCore" target="_blank">
            <strong>Abp.ZeroCore.IdentityServer4.EntityFrameworkCore</strong></a>包。
        安装它到包含了 DbContext 的项目（对于默认模板是 .EntityFrameworkCore 项目）：
    </p>
	<pre>Install-Package Abp.ZeroCore.IdentityServer4.EntityFrameworkCore</pre>
	<p>Then you can add dependency to your 
	<a href="../Module-System.html">module</a> (generally, to your 
    <p class="translation">
        然后，你可以添加依赖到你的<a href="../Module-System.html">模块</a>（一般是添加到你 EntityFrameworkCore 项目）：
    </p>
<pre lang="cs"><strong>[DependsOn(typeof(AbpZeroCoreIdentityServerEntityFrameworkCoreModule))]</strong>
public class MyModule : AbpModule
{
    //...
}
</pre>
	<h3>Configuration - 配置</h3>
	<p>Configuring and using IdentityServer4 with Abp.ZeroCore is similar to 
	independently use IdentityServer4. You should read it&#39;s
	<a href="https://identityserver4.readthedocs.io" target="_blank">own 
	documentation</a> to understand and use it. In this document, we only show 
    <p class="translation">
        与 Abp.ZeroCore 一起配置和使用 IdentityServer4 与独立使用 IdentityServer4 类似。
        你应该阅读<a href="https://identityserver4.readthedocs.io">它的文档</a>来了解和使用它。在本文档，我们仅展示集成到 Abp.ZeroCore 所需要的附加配置。
    </p>
	<h4>Startup Class - 启动类</h4>
	<p>In the ASP.NET Core <strong>Startup class</strong>, we should add IdentityServer to 
	<strong>service collection</strong> and to ASP.NET Core <strong>middleware pipeline</strong>. 
    <p class="translation">
        在 ASP.NET Core <strong>启动类</strong>中，我们应该添加 IdentityServer 到<strong>服务集合</strong>及 ASP.NET Core 的<strong>中间件管道</strong>。
        高亮部分是与标准 IdentityServer4 用法的<strong>差异</strong>：
    </p>
	<pre lang="cs">public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        //...
        
            services.AddIdentityServer()
                .AddDeveloperSigningCredential()
                .AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources())
                .AddInMemoryApiResources(IdentityServerConfig.GetApiResources())
                .AddInMemoryClients(IdentityServerConfig.GetClients())
                .AddAbpPersistedGrants&lt;IAbpPersistedGrantDbContext&gt;()
                .AddAbpIdentityServer&lt;User&gt;(); ;

        //...
    }

    public void Configure(IApplicationBuilder app)
    {
        //...

            app.UseJwtTokenMiddleware("IdentityBearer");
            app.UseIdentityServer();
            
        //...
    }
}</pre>

<p>
    Added <strong>services.AddIdentityServer()</strong> just after
    <strong>IdentityRegistrar.Register(services)</strong> and added 
    <strong>app.UseJwtTokenMiddleware("IdentityBearer&quot;)</strong> just 
    after <strong>app.UseAuthentication()</strong> in the 
    startup project.</p>
<p class="translation">
    在启动项目中的 <strong>IdentityRegistrar.Register(services)</strong> 之后添加了 <strong>services.AddIdentityServer()</strong> ，
    以及 <strong>app.UseAuthentication()</strong> 之后添加了 <strong>app.UseJwtTokenMiddleware("IdentityBearer")</strong>。
</p>
	<h4>IdentityServerConfig Class</h4>
	<p>We have used IdentityServerConfig class to get identity resources, api 
	resources and clients. You can find more information about this class in 
	it&#39;s own
	<a href="https://identityserver4.readthedocs.io/en/release/quickstarts/1_client_credentials.html" target="_blank">
	documentation</a>. For the simplest case, it can be a static class like 
    <p class="translation">
        我们使用 IdentityServerConfig 类获取身份资源，API 资源及客户。
        你可以在 IdentityServer4 的<a href="https://identityserver4.readthedocs.io/en/release/quickstarts/1_client_credentials.html" target="_blank">官方文档</a>中查找关于这个类的更多信息。
        在最简单的情况下，它可以是一个静态类：
    </p>
	<pre lang="cs">public static class IdentityServerConfig
{
    public static IEnumerable&lt;ApiResource&gt; GetApiResources()
    {
        return new List&lt;ApiResource&gt;
        {
            new ApiResource("default-api", "Default (all) API")
        };
    }

    public static IEnumerable&lt;IdentityResource&gt; GetIdentityResources()
    {
        return new List&lt;IdentityResource&gt;
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResources.Email(),
            new IdentityResources.Phone()
        };
    }

    public static IEnumerable&lt;Client&gt; GetClients()
    {
        return new List&lt;Client&gt;
        {
            new Client
            {
                ClientId = "client",
                AllowedGrantTypes = GrantTypes.ClientCredentials.Union(GrantTypes.ResourceOwnerPassword).ToList(),
                AllowedScopes = {"default-api"},
                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                }
            }
        };
    }
}</pre>
	<h4>DbContext Changes - DbContext 的变化</h4>
	persistent data store. In order to use it, <strong>YourDbContext</strong> must implement
    <p class="translation">
        <strong>AddAbpPersistentGrants()</strong> 方法被用来保存赞同的响应到持久化数据存储。
        为了使用它，<strong>YourDbContext</strong> 必须实现 <strong>IAbpPersistedGrantDbContext</strong> 接口，如下所示：
    </p>
	<pre lang="cs">public class YourDbContext : AbpZeroDbContext&lt;Tenant, Role, User, YourDbContext&gt;, <strong>IAbpPersistedGrantDbContext</strong>
{
    <strong>public DbSet&lt;PersistedGrantEntity&gt; PersistedGrants { get; set; }</strong>

    public YourDbContext(DbContextOptions&lt;YourDbContext&gt; options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        <strong>modelBuilder.ConfigurePersistedGrantEntity();</strong>
    }
}</pre>
	<p>IAbpPersistedGrantDbContext defines <strong>PersistedGrants</strong> 
	DbSet. We also should call modelBuilder.<strong>ConfigurePersistedGrantEntity()</strong> 
	extension method as shown above in order to configure EntityFramework for
    <p class="translation">
        IAbpPersistedGrantDbContext 定义了 <strong>PersistedGrants</strong> DbSet。
        我们还应该调用 modelBuilder.<strong>ConfigurePersistedGrantEntity()</strong> 扩展方法，如上所示的那样来为 <strong>PersistedGrantEntity</strong> 配置 EntityFraemwork。
    </p>
	<p>Notice that this change in YourDbContext cause a new database migration. 
	So, remember to use &quot;Add-Migration&quot; and &quot;Update-Database&quot; commands to update 
    <p class="translation">
        注意，在 YourDbContext 的这种改变会造成一个新的数据库迁移。所以，记住要使用“Add-Migration”和“Update-Database”命令来更新你的数据库。
    </p>
	<p>IdentityServer4 will continue to work even if you don&#39;t call 
	AddAbpPersistedGrants&lt;YourDbContext&gt;() extension method, but user consent 
	responses will be stored an in-memory data store in that case (which is 

    <p class="translation">
        即使你不调用 AddAbpPersistedGrants&lt;YourDbContext&gt;() 扩展方法，IdentityServer4 也将继续工作，但用户赞同的响应将被存储到内存数据存储中（当你重新启动应用程序时将被清除）。
    </p>
	<h4>JWT Authentication Middleware</h4>
	<h4>JWT Authentication Middleware - JWT 认证中间件</h4>
	<p>If we want to authorize clients against the same application we can use
	<a href="http://docs.identityserver.io/en/release/topics/apis.html?highlight=UseIdentityServerAuthentication#the-identityserver-authentication-middleware" target="_blank">
    <p class="translation">
        如果我们想以相同的应用程序为背景来授权客户，我们可以在这使用 <a href="http://docs.identityserver.io/en/release/topics/apis.html?highlight=UseIdentityServerAuthentication#the-identityserver-authentication-middleware" target="_blank">IdentityServer 授权中间件</a>。
    </p>
	<p>First, install IdentityServer4.AccessTokenValidation package from nuget 
    <p class="translation">
        首先，从 nuget 安装 IdentityServer4.AccessTokenValidation 包到你的项目：
    </p>
	<pre>Install-Package IdentityServer4.AccessTokenValidation</pre>
    <p class="translation">然后，我们可以添加中件间到启动类，如下所示：</p>
    <pre lang="cs">            services.AddAuthentication().AddIdentityServerAuthentication("IdentityBearer", options =&gt;
            {
                options.Authority = "http://localhost:62114/";
                options.RequireHttpsMetadata = false;
            });
		<div class="bs-callout bs-callout-warning">

			<h4>IdentityServer4.AccessTokenValidation Status - IdentityServer4.AccessTokenValidation 状态</h4>
			<p><em>IdentityServer4.AccessTokenValidation</em> package is not 
			ready for ASP.NET Core 2.0 yet (at the time we write this document). 
            <p class="translation">
                此部分已无效，现已支持 ASP.NET Core 2.0。
            </p>
		</div>
	<h3>Test - 测试</h3>
	<p>Now, our identity server is ready to get requests from clients. We can 
    <p class="translation">
        现在，我们的身份服务器已准备好从客户端获取请求。我们可以创建一个控制台应用程序来发出请求并取得响应。
    </p>
	<ul>
        <p class="translation">在你的解决方案中创建一个新的 <strong>控制台应用程序</strong>。</p>
        <p class="translation">添加 <strong>IdentityModel</strong> nuget 包到控制台应用程序。这个包是被用来为 OAuth 终结点创建客户端。</p>


	</ul>
	<p>While <strong>IdentityModel</strong> nuget package is enough to create a client and consume your API, I want to 
	show to use API in more type safe way: We will convert incoming data to DTOs 
    <p class="translation">
        虽然 <strong>IdentityModel</strong> nuget 包足以创建客户端及消费你的 API，但我想展示以类型安全的方式使用 API：我们将转换收到的数据为 DTO，并通过应用服务返回。
    </p>
	<ul>
		application. This will allow us to use same DTO classes returned by the 
        <p class="translation">从控制台应用程序添加<strong>应用</strong>层的引用。这将允许我们使用相同的 DTO 类通过客户端应用层返回。</p>

		us to use AjaxResponse class defined in ASP.NET Boilerplate class. 
		Otherwise, we will deal with raw JSON strings to handle the server 
        <p class="translation">
            添加 <strong>Abp.Web.Common</strong> nuget 包。
            这将允许我们使用定义在 ASP.NET Boilerplate 中的 AjaxResponse 类。
            否则，我们将使用原生 JSON 字符串来处理服务器响应。
        </p>
	</ul>
    <p class="translation">然后，我们可按如下修改 Program.cs：</p>
	<pre lang="cs">using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Abp.Application.Services.Dto;
using Abp.Json;
using IdentityModel.Client;
using Abp.MultiTenancy;
using Abp.Web.Models;
using IdentityServerIntegrationDemo.Users.Dto;
using Newtonsoft.Json;

namespace IdentityServerIntegrationDemo.ConsoleApiClient
{
    class Program
    {
        static void Main(string[] args)
        {
            RunDemoAsync().Wait();
            Console.ReadLine();
        }

        public static async Task RunDemoAsync()
        {
            var accessToken = await GetAccessTokenViaOwnerPasswordAsync();
            await GetUsersListAsync(accessToken);
        }

        private static async Task&lt;string&gt; GetAccessTokenViaOwnerPasswordAsync()
        {
            var disco = await DiscoveryClient.GetAsync("http://localhost:62114");

<strong>            var httpHandler = new HttpClientHandler();
            httpHandler.CookieContainer.Add(new Uri("http://localhost:62114/"), new Cookie(MultiTenancyConsts.TenantIdResolveKey, "1")); //Set TenantId
            var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret", httpHandler);
            var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("admin", "123qwe");
</strong>
            if (tokenResponse.IsError)
            {
                Console.WriteLine("Error: ");
                Console.WriteLine(tokenResponse.Error);
            }

            Console.WriteLine(tokenResponse.Json);

            return tokenResponse.AccessToken;
        }

        private static async Task GetUsersListAsync(string accessToken)
        {
            var client = new HttpClient();
            client.SetBearerToken(accessToken);

<strong>            var response = await client.GetAsync("http://localhost:62114/api/services/app/user/GetAll");
</strong>            if (!response.IsSuccessStatusCode)
            {
                Console.WriteLine(response.StatusCode);
                return;
            }

            var content = await response.Content.ReadAsStringAsync();
<strong>            var ajaxResponse = JsonConvert.DeserializeObject&lt;AjaxResponse&lt;PagedResultDto&lt;UserListDto&gt;&gt;&gt;(content);
</strong>            if (!ajaxResponse.Success)
            {
                throw new Exception(ajaxResponse.Error?.Message ?? "Remote service throws exception!");
            }

            Console.WriteLine();
            Console.WriteLine("Total user count: " + ajaxResponse.Result.TotalCount);
            Console.WriteLine();
            foreach (var user in ajaxResponse.Result.Items)
            {
                Console.WriteLine($"### UserId: {user.Id}, UserName: {user.UserName}");
                Console.WriteLine(user.ToJsonString(indented: true));
            }
        }
    }
    
    internal class UserListDto
    {
        public int Id { get; set; }
        public string UserName { get; set; }
    }
}</pre>
	<p>Before running this application, ensure that your web project is up and 
	running, because this console application will make request to the web 
	application. Also, ensure that the requesting port (62114) is the same of 
    <p class="translation">
        在运行这个应用程序之前，请确保你的 web 项目已启动并运行，因为这个控制台应用程序将请求 web 应用程序。
        同样，请确保请求端口（62114）与你的 web 应用程序的相同。
    </p>
</div>

</body>

</html>
